package com.zx.idc.backend.gui.api.ftp.sftp;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import com.zx.idc.backend.gui.api.ftp.FtpClientConfig;
import com.zx.idc.backend.gui.api.ftp.FtpOperator;
import com.zx.idc.backend.gui.api.ftp.Stat;
import com.zx.idc.common.function.AllowErrorFunction;
import com.zx.idc.common.lang.PathUtil;
import com.zx.idc.common.pool.BoundedBlockingPool;
import com.zx.idc.common.pool.Pool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.zx.idc.common.lang.PathUtil.getParentDir;

/**
 * sftp 操作的具体实现
 *
 * @author rxliuli
 */
public class SftpOperator implements FtpOperator {
    private final Logger log = LoggerFactory.getLogger(getClass());

    private final Pool<ChannelSftp> channelSftpPool;

    public SftpOperator(FtpClientConfig ftpClientConfig) {
        Pool<Session> sessionPool = new BoundedBlockingPool<>(ftpClientConfig.getInitConnections(), new SftpSessionValidator(), new SftpSessionFactory(ftpClientConfig));
        channelSftpPool = new BoundedBlockingPool<>(ftpClientConfig.getInitConnections(), new SftpChannelSftpValidator(), new SftpChannelSftpFactory(sessionPool, ftpClientConfig));
    }

    /**
     * MybatisUtil 中 usingOptional() 方法再封装而已
     *
     * @param f   执行方法
     * @param <R> 执行方法
     * @return 方法的返回值
     */
    private <R> R using(AllowErrorFunction<ChannelSftp, R> f) {
        final ChannelSftp sftp = channelSftpPool.get();
        try {
            return f.apply(sftp);
        } catch (Throwable e) {
            log.error("sftp operation failed: {}", e);
            throw new RuntimeException(e);
        } finally {
            channelSftpPool.release(sftp);
        }
    }

    @Override
    public List<Stat> ls(String path) {
        //noinspection unchecked
        final Vector<ChannelSftp.LsEntry> vector = using(sftp -> sftp.ls(path));
        return vector.stream()
            .map(lsEntry ->
                new Stat(
                    lsEntry.getFilename(),
                    PathUtil.join(path, lsEntry.getFilename()),
                    lsEntry.getAttrs().isDir(),
                    lsEntry.getAttrs().getSize()
                )
            )
            .filter(stat -> !stat.getName().equals(PathUtil.CURRENT_DIR) && !stat.getName().equals(PathUtil.PARENT_DIR))
            .collect(Collectors.toList());
    }

    @Override
    public boolean get(String remoteFilePath, File localFile, boolean isCreateFile) {
        if (isCreateFile) {
            final boolean mkdirs = localFile.getParentFile().mkdirs();
            log.info("create file successful: {}", mkdirs);
        }
        return using(sftp -> {
            try (final OutputStream os = new FileOutputStream(localFile)) {
                sftp.get(remoteFilePath, os);
                return true;
            } catch (SftpException e) {
                log.error("sftp get operation failed: {}", e);
                return false;
            }
        });
    }

    @Override
    public <R> R get(String remoteFilePath, Function<InputStream, R> action) {
        return using(sftp -> {
            try (InputStream is = sftp.get(remoteFilePath)) {
                return action.apply(is);
            } catch (SftpException e) {
                log.error("sftp get operation failed: {}", e);
                return null;
            }
        });
    }

    @Override
    public boolean put(InputStream localIs, String remoteFilePath, boolean isCreateDir) {
        if (isCreateDir) {
            mkdirR(getParentDir(remoteFilePath));
        }
        return using(sftp -> {
            try {
                sftp.put(localIs, remoteFilePath);
                return true;
            } catch (SftpException e) {
                log.error("sftp put operation failed: {}", e);
                return false;
            } finally {
                if (localIs != null) {
                    localIs.close();
                }
            }
        });
    }

    @Override
    public boolean rm(String path) {
        return using(sftp -> {
            try {
                sftp.rm(path);
                return true;
            } catch (SftpException e) {
                log.error("sftp rm operation failed: {}", e);
                return false;
            }
        });
    }

    @Override
    public boolean rmdir(String path) {
        return using(sftp -> {
            try {
                sftp.rmdir(path);
                return true;
            } catch (SftpException e) {
                log.error("sftp rmdir operation failed: {}", e);
                return false;
            }
        });
    }

    @Override
    public boolean mkdir(String path) {
        return using(sftp -> {
            try {
                sftp.mkdir(path);
                return true;
            } catch (SftpException e) {
                log.error("sftp mkdir operation failed: {}", e);
                return false;
            }
        });
    }

    @Override
    public boolean exist(String path) {
        return using(sftp -> {
            final SftpATTRS stat;
            try {
                stat = sftp.lstat(path);
            } catch (SftpException e) {
                log.info("sftp exist operation failed: {}", e.getMessage());
                return false;
            }
            return stat.isDir() || stat.getSize() >= 0;
        });
    }

    @Override
    public boolean downloadFile(String srcFilePath, File targetFilePath) {
        return using(sftp -> {
            try (FileOutputStream out = new FileOutputStream(targetFilePath);) {
                String[] split = srcFilePath.split("/");
                String[] strings = Arrays.copyOf(split, split.length - 1);
                sftp.cd(String.join("/", strings));
                sftp.get(srcFilePath, out);
                return true;
            }
        });
    }

    @Override
    public void closePool() {
        channelSftpPool.shutdown();
    }

}
